package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CategoryClient;
import com.tianji.api.dto.course.CategoryBasicDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.*;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.promotion.contants.PromotionContants.COUPON_CACHE_KEY_PREFIX;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author xzh
 * @since 2024-03-20
 */
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;
    private final IExchangeCodeService exchangeCodeService;
    private final CategoryClient categoryClient;
    private final IUserCouponService userCouponService;
    private final StringRedisTemplate redisTemplate;

    /**
     * 新增优惠券
     *
     * @param couponFormDTO
     */
    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO couponFormDTO) {

        //获得新增优惠券人的id
        Long userId = UserContext.getUser();
        //拷贝
        Coupon coupon = BeanUtils.copyBean(couponFormDTO, Coupon.class);
        coupon.setLimitation(couponFormDTO.getSpecific());
        coupon.setCuoponName(couponFormDTO.getName());
        //获得name
        coupon.setCreater(userId);
        coupon.setCreateTime(LocalDateTime.now());
        coupon.setStatus(CouponStatus.DRAFT);
        boolean save = save(coupon);
        if (!save) {
            throw new DbException("新增失败");
        }
        //判断优惠券有没有限定范围 有则要保存到couponScope表中
        if (!couponFormDTO.getSpecific()) {
            //没有限定范围
            return;
        }
        //限定范围 保存限定范围 CouponScope
        saveCouponScope(couponFormDTO, coupon);
    }

    /**
     * 保存限定范围 CouponScope
     *
     * @param couponFormDTO
     * @param coupon
     */
    private void saveCouponScope(CouponFormDTO couponFormDTO, Coupon coupon) {

        List<Long> list = couponFormDTO.getScopes();
        if (CollUtils.isEmpty(list)) {
            throw new BadRequestException("限定范围不能为空");
        }
        //保存
        Long couponId = coupon.getId();

        List<CouponScope> scopeList = list.stream()
                .map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId))
                .collect(Collectors.toList());

        boolean b = couponScopeService.saveBatch(scopeList);
        if (!b) {
            throw new DbException("优惠券使用范围保存失败");
        }
    }

    /**
     * 优惠券分页查询
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery query) {
        String name = query.getName();
        // 1.根据条件查询出需要的优惠券
        Page<Coupon> page = lambdaQuery()
                .eq(query.getType() != null, Coupon::getType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(name), Coupon::getCuoponName, name)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 2.po转vo
        List<CouponPageVO> list=new ArrayList<>(records.size());
        for (Coupon r : records) {
            CouponPageVO vo = BeanUtils.copyBean(r, CouponPageVO.class);
            vo.setName(r.getCuoponName());
            vo.setSpecific(r.getLimitation());
            list.add(vo);
        }

        return PageDTO.of(page, list);
    }

    /**
     * 发放待发放和暂停中状态的优惠券
     *
     * @param id
     * @param formDTO
     */
    @Override
    @Transactional
    public void issueCouponByStatus(Long id, CouponIssueFormDTO formDTO) {
        //逻辑:1.状态为待发放和暂停中的才可以发行 2.如果发行时间为空则是立即发行3.如果有效天数为空则是固定时间段使用
        //判断是否符合逻辑1
        Coupon coupon = lambdaQuery().eq(Coupon::getId, id).one();
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        CouponStatus status = coupon.getStatus();
        if (status != CouponStatus.DRAFT && status != CouponStatus.PAUSE) {
            throw new BizIllegalException("此优惠券不能发行");
        }
        //判断逻辑2
        LocalDateTime now = LocalDateTime.now();

        if (formDTO.getIssueBeginTime() == null || formDTO.getIssueBeginTime().isBefore(now)) {
            coupon.setIssueBeginTime(now);
            coupon.setIssueEndTime(formDTO.getTermEndTime());
        } else {
            coupon.setIssueBeginTime(formDTO.getIssueBeginTime());
            coupon.setIssueEndTime(formDTO.getIssueEndTime());
        }
        //判断逻辑3
        if (formDTO.getTermDays() == null) {
            coupon.setTermBeginTime(formDTO.getTermBeginTime());
            coupon.setTermEndTime(formDTO.getTermEndTime());
        } else {
            coupon.setTermDays(formDTO.getTermDays());
        }
        //修改状态
        coupon.setUsedNum(UserContext.getUser().intValue());
        coupon.setUpdateTime(now);
        coupon.setStatus(CouponStatus.UN_ISSUE);

        //更新
        updateById(coupon);
        //6.用户异步领取优惠券  发放新增 暂停删除 立即发放 稍后发放写方法
        if (formDTO.getIssueBeginTime() == null ){
            coupon.setIssueBeginTime(now);
            coupon.setIssueEndTime(formDTO.getIssueEndTime());
            cacheCouponInfo(coupon);
        }


        // 生成兑换码 条件 优惠券状态必须为代发行  领取方式为 兑换码
        if (status == CouponStatus.DRAFT && coupon.getObtainWay() == ObtainType.ISSUE) {
            coupon.setIssueEndTime(formDTO.getIssueEndTime());
            exchangeCodeService.saveExchangeCode(coupon);
        }





    }
    private void cacheCouponInfo(Coupon coupon) {
        // 1.组织数据
        Map<String, String> map = new HashMap<>(4);
        map.put("issueBeginTime", String.valueOf(coupon.getIssueBeginTime()));
        map.put("issueEndTime", String.valueOf(coupon.getIssueEndTime()));
        map.put("totalNum", String.valueOf(coupon.getTotalNum()));
        map.put("userLimit", String.valueOf(coupon.getUserLimit()));
        // 2.写缓存
        redisTemplate.opsForHash().putAll(COUPON_CACHE_KEY_PREFIX + coupon.getId(), map);
    }

    /**
     * 编辑优惠券信息
     *
     * @param couponFormDTO
     */
    @Override
    public void updateCouponById(CouponFormDTO couponFormDTO) {
        //只能修改代发放的优惠券
        Coupon coupon = lambdaQuery().eq(Coupon::getId, couponFormDTO.getId()).one();
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        if (coupon.getStatus() != CouponStatus.DRAFT) {
            throw new BadRequestException("只能修改待发放的优惠券信息");
        }
        // 拷贝
        Coupon updateCoupon = BeanUtils.copyBean(couponFormDTO, Coupon.class);
        coupon.setLimitation(couponFormDTO.getSpecific());
        coupon.setCuoponName(couponFormDTO.getName());
        updateCoupon.setUpdater(UserContext.getUser());
        updateCoupon.setUpdateTime(LocalDateTime.now());
        //更新
        updateById(updateCoupon);

        //判断有没有修改或等加限定范围
        //1.删除限定范围 都可以用
        if (coupon.getLimitation() && !couponFormDTO.getSpecific()) {
            couponScopeService.remove(new QueryWrapper<CouponScope>()
                    .lambda().eq(CouponScope::getCouponId, couponFormDTO.getId()));
        }
        //2.新增限定范围
        if (!coupon.getLimitation() && couponFormDTO.getSpecific()) {
            saveCouponScope(couponFormDTO, coupon);
        }
        //3.修改限定范围
        List<CouponScope> list = couponScopeService
                .lambdaQuery().eq(CouponScope::getCouponId, couponFormDTO.getId()).list();
        if (CollUtils.isEmpty(list)) {
            return;
        }
        List<Long> oldList = list.stream().map(CouponScope::getBizId).collect(Collectors.toList());
        boolean success = false;
        for (int i = 0; i < oldList.size(); i++) {
            if (!Objects.equals(oldList.get(i), couponFormDTO.getScopes().get(i))) {
                success = true;
                break;
            }
        }
        if (success) {
            List<Long> collect = list.stream().map(CouponScope::getId).collect(Collectors.toList());
            List<CouponScope> newList = new ArrayList<>(collect.size());
            CouponScope couponScope = new CouponScope();
            for (int i = 0; i < collect.size(); i++) {
                couponScope.setId(collect.get(i));
                couponScope.setBizId(couponFormDTO.getScopes().get(i));
                newList.add(couponScope);
            }
            couponScopeService.updateBatchById(newList);
        }
    }

    /**
     * 删除优惠券
     *
     * @param id
     */
    @Override
    public boolean deleteCouponAndScope(Long id) {
        // 逻辑 只有优惠券状态为待发放的才能删除 如果有关联限定范围需一并删除
        Coupon coupon = lambdaQuery().eq(Coupon::getId, id).one();
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        //判断优惠券状态
        if (coupon.getStatus() != CouponStatus.DRAFT) {
            throw new BadRequestException("优惠券不能被删除");
        }
       removeById(id);

        //删除关联字段
        if (coupon.getLimitation()) {
            couponScopeService.remove(new QueryWrapper<CouponScope>()
                    .lambda().eq(CouponScope::getCouponId, id));
        }

        //6.用户异步领取优惠券  删除redis中对应的数据
        String key= COUPON_CACHE_KEY_PREFIX+id;
        Boolean result = redisTemplate.delete(key);
        return Boolean.TRUE.equals(result);
    }

    /**
     * 根据id查询优惠券详细信息
     *
     * @param id
     * @return
     */
    @Override
    public CouponDetailVO getCouponDetail(Long id) {
        //逻辑 待发放的不能查看
        Coupon coupon = lambdaQuery().eq(Coupon::getId,id).one();
        if (coupon == null || coupon.getStatus() == CouponStatus.DRAFT) {
            throw new BadRequestException("不能查看优惠券详情");
        }
        // 拷贝
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        vo.setName(coupon.getCuoponName());

        //如果有限定范围则添加限定范围
        if (!coupon.getLimitation()) {
            return vo;
        }
        List<CouponScope> scopeList = couponScopeService.lambdaQuery()
                .eq(CouponScope::getCouponId, id).list();
        if (CollUtils.isEmpty(scopeList)) {
            throw new DbException("限定范围数据查询异常");
        }
        List<Long> bizIds = scopeList.stream().map(CouponScope::getBizId).collect(Collectors.toList());
        //根据分类id 获得分类名称
        Map<Long, String> map = categoryClient.getAllOfOneLevel().stream()
                .collect(Collectors.toMap(CategoryBasicDTO::getId, CategoryBasicDTO::getName));
        List<CouponScopeVO> list = new ArrayList<>(bizIds.size());
        for (int i = 0; i < bizIds.size(); i++) {
            CouponScopeVO scopeVO = new CouponScopeVO();
            scopeVO.setId(bizIds.get(i));
            scopeVO.setName(map.get(bizIds.get(i)));
            list.add(scopeVO);
        }
        vo.setScopes(list);
        return vo;
    }

    /**
     * 将发放中的优惠券改为暂停发放
     *
     * @param id
     */
    @Override
    @Transactional
    public void stopIssueCouponAndExchangeCodeById(Long id) {
        //将发放中的优惠券改为暂停发放
        Coupon coupon = lambdaQuery().eq(Coupon::getId, id).one();
        if (coupon == null) {
            throw new BadRequestException("没有此优惠券");
        }
        if (coupon.getStatus() != CouponStatus.ISSUING) {
            throw new BadRequestException("此优惠券不能暂停发放");
        }
        coupon.setStatus(CouponStatus.PAUSE);
        coupon.setUpdater(UserContext.getUser());
        coupon.setUpdateTime(LocalDateTime.now());
        updateById(coupon);
        //如果领取方式为指定发放 则需要暂停兑换码
        if (coupon.getObtainWay() == ObtainType.ISSUE) {
            exchangeCodeService.lambdaUpdate().eq(ExchangeCode::getExchangeTargetId, id)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.EXPIRED)
                    .set(ExchangeCode::getUpdateTime, LocalDateTime.now());
        }



        //6.用户异步领取优惠券 暂停优惠券就是删除redis
        String key= COUPON_CACHE_KEY_PREFIX+id;
        redisTemplate.delete(key);
    }

    /**
     * 定时扫秒将未开始状态以及开始时间已到或已过的优惠券状态设置成发放中 同时发送其对应的code
     */
    @Override
    @Transactional
    public void taskIssuesCouponAndCode() {
        //1.得到未开始的优惠券 对比开始时间
        LocalDateTime now = LocalDateTime.now();
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.UN_ISSUE)
                .le(Coupon::getIssueBeginTime, now)
                .list();
        if (CollUtils.isEmpty(coupons)) {
            return;
        }
        //2.将状态改为发放中
        List<Long> couponIds = coupons.stream().map(Coupon::getId).collect(Collectors.toList());

        lambdaUpdate().in(Coupon::getId, couponIds).set(Coupon::getStatus, CouponStatus.ISSUING);

        //3.查找有兑换码的
        List<Long> collect = coupons.stream()
                .filter(c -> c.getObtainWay().equals(ObtainType.ISSUE))
                .map(Coupon::getId).collect(Collectors.toList());
        if (CollUtils.isEmpty(collect)) {
            return;
        }
        //4.将兑换码状态设为待兑换
        exchangeCodeService.lambdaUpdate()
                .in(ExchangeCode::getExchangeTargetId, collect)
                .set(ExchangeCode::getStatus, ExchangeCodeStatus.UNUSED);


    }

    /**
     * 定时将发送时间已过的优惠券状态设为已结束 包括对应兑换码
     */
    @Override
    @Transactional
    public void taskStopIssuesCouponAndCode() {
        //1.查询已到过期时间的发行中和已暂停的优惠券
        LocalDateTime now = LocalDateTime.now();
        List<Coupon> coupons = lambdaQuery()
                .in(Coupon::getStatus, CouponStatus.ISSUING, CouponStatus.PAUSE)
                .le(Coupon::getIssueEndTime, now)
                .list();
        if (CollUtils.isEmpty(coupons)) {
            return;
        }
        //2.将优惠券状态设为结束
        List<Long> couponIds = coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        lambdaUpdate()
                .in(Coupon::getId, couponIds)
                .set(Coupon::getStatus, CouponStatus.FINISHED)
                .update();

        //3.判断有没有对应的兑换码

        List<Long> collect = coupons.stream()
                .filter(c -> c.getObtainWay().equals(ObtainType.ISSUE))
                .map(Coupon::getId).collect(Collectors.toList());
        if (CollUtils.isEmpty(collect)) {
            return;
        }
        //4.将兑换码状态设为结束
        exchangeCodeService.lambdaUpdate()
                .in(ExchangeCode::getExchangeTargetId, collect)
                .set(ExchangeCode::getStatus, ExchangeCodeStatus.EXPIRED)
                .update();

        //6.用户异步领取优惠券 将redis中对应的优惠券删除
        List<Long> redis = coupons.stream().filter(c -> c.getObtainWay()
                .equals(ObtainType.PUBLIC)).map(Coupon::getId).collect(Collectors.toList());

        if (CollUtils.isEmpty(redis)){
            return;
        }
        List<String> keys = new ArrayList<>(redis.size());
        for (Long id : redis) {
            String key= COUPON_CACHE_KEY_PREFIX+id;
            keys.add(key);
        }
        redisTemplate.delete(keys);
    }

    /**
     * 向用户展示优惠券详情
     *
     * @return
     */
    @Override
    public List<CouponVO> queryIssuingCoupons() {

        //1.查询出所有进行中的手动领取的优惠券详情
        List<Coupon> couponList = lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (CollUtils.isEmpty(couponList)) {
            return CollUtils.emptyList();
        }
        //2.查询和统计当前用户领取优惠券的id和已领取数量 状态
        List<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toList());

        List<UserCoupon> userCouponList = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId,UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();


        //2.1统计用户已领取某种优惠券的数量 用couponId分组
        Map<Long, Long> issusedMap = userCouponList.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //2.2统计用户已领取某种优惠券并且没有使用的数量 用couponId分组
        Map<Long, Long> unIssusedMap = userCouponList.stream()
                .filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //3.判断用户是否可领取  是否可使用
        List<CouponVO> list = new ArrayList<>(couponList.size());
        for (Coupon coupon : couponList) {
            //拷贝
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            vo.setSpecific(coupon.getLimitation());
            vo.setName(coupon.getCuoponName());
            if (CollUtils.isEmpty(userCouponList)) {
                vo.setAvailable(coupon.getIssueNum() < coupon.getTotalNum());
                vo.setReceived(false);
            }
            //是否可以领取 已领取数<每人限领数&&还有剩余
            vo.setAvailable(issusedMap.getOrDefault(
                    coupon.getId(), 0L) < coupon.getUserLimit()
                    && coupon.getIssueNum() < coupon.getTotalNum()
            );
            //是否可以使用 用户券有且没有使用
            vo.setReceived(unIssusedMap.getOrDefault(coupon.getId(), 0L) > 0);
            list.add(vo);
        }
        //分装返回
        return list;
    }
}
